/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;
using System.Collections.Generic;
using System.IO;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace Cobalt.Framework.Bsp
{
	using Graphics;

	partial class BspData
	{
		public sealed class Pvs : IPvs
		{
			private BspData owner;
			internal Pvs( BspData owner )
			{
				this.owner = owner;

				pvsFlags = new uint[(owner.leaves.Length + owner.partitions.Length) / 32 + 1];
			}

			public BspData Owner
			{
				get { return owner; }
			}

			internal uint[] pvsFlags;

			public void MarkAllVisible()
			{
				for( int i = 0; i < pvsFlags.Length; i++ )
					pvsFlags[i] = 0xFFFFFFFF;
			}

			private unsafe void Mark( int clusterNum )
			{
				if( clusterNum < 0 || clusterNum >= owner.pvs.NumClusters )
					throw new ArgumentOutOfRangeException( "clusterNum" );

				Array.Clear( pvsFlags, 0, pvsFlags.Length );

				int clusterWords = owner.pvs.NumClusters / 32 + 1;

				fixed( uint* ppvsFlags = &pvsFlags[0] )
				fixed( uint* pvsData = &owner.pvs.PvsBytes[clusterNum * clusterWords] )
				{
					foreach( LeafNode leaf in owner.leaves )
					{
						int cluster = leaf.pvsCluster;

						if( cluster < 0 || cluster >= owner.pvs.NumClusters )
							continue;

						if( (pvsData[cluster >> 5] & (1 << (cluster & 0x1F))) == 0 )
							continue;

						for( Node n = leaf; n != null; n = n.parent )
							ppvsFlags[n.pvsIndex >> 5] |= 1U << (n.pvsIndex & 0x1F);
					}
				}
			}

			public void Mark( Vector3 cameraPosition )
			{
				if( owner.pvs.PvsBytes.Length == 0 ||
					owner.partitions.Length == 0 )
				{
					MarkAllVisible();
					return;
				}

				LeafNode viewLeaf = owner.PointToLeaf( cameraPosition );

				if( viewLeaf == null || viewLeaf.pvsCluster < 0 ||
					viewLeaf.pvsCluster >= owner.pvs.NumClusters )
				{
					MarkAllVisible();
					return;
				}

				Mark( viewLeaf.pvsCluster );				
			}
		}

		private uint cullCounter;
		private BoundingFrustum cullFrustum = new BoundingFrustum( Matrix.Identity );

		private unsafe void AppendCulledSurfaces( DrawList surfList,
			int matrixId, ShadingContext context, Node node, uint *pvsFlags )
		{
			for( ; ; )
			{
				if( (pvsFlags[node.pvsIndex >> 5] & (1U << (node.pvsIndex & 0x1F))) == 0 )
					return;

				ContainmentType containType;
				cullFrustum.Contains( ref node.bounds, out containType );
				if( containType == ContainmentType.Disjoint )
					return;

				foreach( Surface surf in node.surfaces )
				{
					if( surf.addCounter == cullCounter )
						continue;

					surf.addCounter = cullCounter;
					surfList.AddDrawSurface( surf, context.OverrideMaterial ?? surf.Material, matrixId );
				}

				PartitionNode part = node as PartitionNode;

				if( part == null )
					return;

				if( part.child0 != null )
					AppendCulledSurfaces( surfList, matrixId, context, part.child0, pvsFlags );

				if( part.child1 == null )
					return;

				node = part.child1;
			}
		}

		private unsafe void AppendCulledSurfaces( DrawList surfList,
			ref Matrix modelMatrix, ShadingContext context, Pvs pvs )
		{
			cullCounter = unchecked( cullCounter + 1 );
			if( cullCounter == 0 )
				cullCounter = 1;

			int matrixID = surfList.RegisterMatrix( modelMatrix );
			cullFrustum.Matrix = modelMatrix * context.ViewProjection;

			fixed( uint *pvsFlags = &pvs.pvsFlags[0] )
				AppendCulledSurfaces( surfList, matrixID, context, partitions[0], pvsFlags );
		}

		public Pvs CreatePvs()
		{
			return new Pvs( this );
		}

		IPvs ISurfaceList.CreatePvs()
		{
			return CreatePvs();
		}
	}

}